function dump_exception(e, instance) {
  s = [];
  if (instance) {
    s.push(instance.url);
    s.push("msie_access_denied_error=" + instance._msie_access_denied_error);
    s.push("opera_send_security_violation=" + instance._opera_send_security_violation);
    s.push("firefox_malformed_uri_error=" + instance._firefox_malformed_uri_error);
  }
  for (var i in e) {
    try { l = i + "=" + e[i]; } catch(e) { l = i + "=[access denied]"; }
    s.push(l);
  }
  alert(s.join("\r\n"));
}

function AsyncXMLHttpRequest() {
  // An AsyncXMLHttpRequest object works as a wrapper for a real XMLHttpRequest
  // object. The user should be able to use the AsyncXMLHttpRequest as if it is
  // a real XMLHttpRequest. In the current implementation there is one exception
  // to this: reading certain attributes of the real XMLHttpRequest can throw
  // an exception, but reading the same attributes from the AsyncXMLHttpRequest
  // object does not: their value will be "undefined". There may be a way to fix
  // that for certain JavaScript implementations, but not for all.
  this._XMLHttpRequest = null;
  if (typeof(XMLHttpRequest) != "undefined") {
    this._XMLHttpRequest = new XMLHttpRequest();
  } else {
    xmlhttp_objects = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", 
        "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
    for (var i in objects) {
      try {
        this._XMLHttpRequest = new ActiveXObject(xmlhttp_objects[i]);
        break;
      } catch (e) {
        this._XMLHttpRequest = null;
        continue;
      }
    }
  }
  if (this._XMLHttpRequest == null) {
    throw new Error("Cannot instanciate an XMLHttpRequest object");
  }

  // AsyncXMLHttpRequest passes the object to which an event belongs as the
  // first argument to the event handler. In order to find out which object to
  // pass, a global array with all active objects is created, which can be
  // searched later. To prevent memory leaks, the objects are removed from the
  // list as soon as their readyState has reached 4 because no more events are
  // expected at that point and they are therefore no longer needed in the list.
  if (typeof(AsyncXMLHttpRequest._instances) == "undefined") {
    AsyncXMLHttpRequest._instances = [this];
  } else {
    AsyncXMLHttpRequest._instances.push(this);
  }

  // AsyncXMLHttpRequest shares the following attributes with XMLHttpRequest:
  //   readyState, responseBody, responseText, responseXML, status, statusText
  // These work exactly the same in AsyncXMLHttpRequest as in XMLHttpRequest
  // To make it easy to update them, this global function is used internally:
  AsyncXMLHttpRequest._copy_attributes = function 
      AsyncXMLHttpRequest_copy_attributes(instance) {
    properties = ["readyState", "responseBody", "responseText",
        "responseXML", "status", "statusText"];
    for (var i in properties) {
      var property = properties[i];
      try {
        instance[property] = instance._XMLHttpRequest[property];
      } catch (e) {
        instance[property] = void(0);
      }
    }
  }
  // Make a copy of the default attributes:
  AsyncXMLHttpRequest._copy_attributes(this);
  // In addition to the default attributes, AsyncXMLHttpRequest has these:
  this.timeout = 0;           // Timeout for the request in milliseconds.
  this.timedout = false;      // Indicates if the request has timed out.
  // If the request does not complete before the timeout, it is aborted using
  // "abort()". "timedout" is set to true and the "ontimeout" event is fired.
  this.method = void(0);      // The method argument used in open()
  this.url = void(0);         // The url argument used in open()
  this.user = void(0);        // The user argument used in open()
  this.pasword = void(0);     // The password argument used in open()
  this.body = void(0);        // The body argument used in send()
  // These five attributes are set automatically when "open()" and "send()" are
  // called for the users' convenience - they add no extra functionality.

  // AsyncXMLHttpRequest shares the following event with XMLHttpRequest:
  this.onreadystatechange = void(0);
  // However, the AsyncXMLHttpRequest object for which the event fires is passed
  // as the first argument to the event handler. This makes it easy to create
  // many parallel requests.
  // In addition to this event, AsyncXMLHttpRequest has these events, which are
  // called when a request is completed:
  this.onload = void(0);      // Called when the server returns success.
  this.onerror = void(0);     // Called when the server returns an error.
  this.ontimeout = void(0);   // Called when the request timed out.
  // Only one of these is called for each request, depending on the outcome; if
  // there is a timeout, the "ontimeout" event is fired but the "onerror" event
  // is not: the "onerror" event is only called when the request does not
  // time out before the server responds with a status code <200 or >299.
  // The AsyncXMLHttpRequest object for which the event fires is passed as the
  // the first argument to the event handler for each of these events.

  // MSIE throws an "access denied" exception when you call open() with a url
  // that is cross-origin. To make this work uniform across browsers, this 
  // exception is handled and the problem is dealt with when send() is called.
  this._msie_access_denied_error = false;
  // Opera throws an "Security violation" exception when you call send() on a
  // XHR with a url that is cross-origin. To make this work uniform across 
  // browsers, this exception is handled and the problem is dealt with when
  // the readystate is changed to 2.
  this._opera_send_security_violation = false;
  // Firefox throws an "malformed URI" exception when you call open() with a
  // invalid url. To make this work uniform across all browser, the exception
  // is handled and the problem is dealt with when send() is called.
  this._firefox_malformed_uri_error = true;

  // Most methods are implemented as wrappers for the XMLHttpRequest object:
  // Wrapper for getAllReponseHeaders()
  this.getAllReponseHeaders = function 
      AsyncXMLHttpRequest_getAllReponseHeaders() {
    this._XMLHttpRequest.getAllReponseHeaders();
  }
  // Wrapper for getReponseHeader(name)
  this.getReponseHeader = function AsyncXMLHttpRequest_getReponseHeader(name) {
    this._XMLHttpRequest.getReponseHeader(name);
  }
  // Wrapper for setRequestHeader(name, value)
  this.setRequestHeader = function 
      AsyncXMLHttpRequest_setRequestHeader(name, value) {
    this._XMLHttpRequest.setRequestHeader(name, value);
  }
  // To make this work uniform across all browsers, some exceptions need to be
  // caught and readystate changes faked. The later can be done with this
  // function
  this._fake_readystatechange = function 
      AsyncXMLHttpRequest_fake_readystatechange(newReadyState) {
    this.status = 0;
    this.readyState = newReadyState;
    if (typeof(this.onreadystatechange) != "undefined") {
      this.onreadystatechange(this);
    }
    if (this.readyState == 4) {
      // The request is done, it will no longer receive events. It can be
      // removed from the global list to prevent memory leaks:
      for (var i in AsyncXMLHttpRequest._thiss) {
        if (AsyncXMLHttpRequest._instances[i] == this) {
          delete AsyncXMLHttpRequest._instances[i];
        }
      }
      // Call onerror because the status is unsuccessful if it exists:
      if (typeof(this.onerror) != "undefined") {
        this.onerror(this);
      }
    }
  }
  // Wrapper for open(method, url, user, password)
  this.open = function AsyncXMLHttpRequest_open(method, url, user, password) {
    // Make a copy of the arguments for the users' convenience:
    this.method = method;
    this.url = url;
    this.user = user;
    this.pasword = password;
    try {
      if (typeof(user) == "undefined") {
        this._XMLHttpRequest.open(method, url, true); // No user/pw supplied
      } else {
        this._XMLHttpRequest.open(method, url, true, user, password);
      }
    } catch (e) {
      if (typeof(e.number) != "undefined" && e.number == -2147024891) {
        // MSIE does not allow opening a url cross-domain. To make this work
        // uniform across all browsers, the exception is handled and a 
        // readystate change to 1 with a status of 0 is "faked":
        this._msie_access_denied_error = true;
        this._fake_readystatechange(1);
      } else if (typeof(e.name) != "undefined" && 
                 e.name == "NS_ERROR_MALFORMED_URI"){
        // Firefox does not allow opening a url that's incorrect. To make this
        // work uniform across all browser, the exception is handled and a 
        // readystate change to 1 with a status of 0 is "faked":
        this._firefox_malformed_uri_error = true;
        this._fake_readystatechange(1);
      } else {
        dump_exception(e, this);
        throw e;
      }
    }
    // Update the attributes of the AsyncXMLHttpRequest object to those of the
    // XMLHttpRequest object:
    AsyncXMLHttpRequest._copy_attributes(this);
  }
  // Wrapper for send(body)
  this.send = function AsyncXMLHttpRequest_send(body) {
    if (this._msie_access_denied_error || this._firefox_malformed_uri_error) {
      // Some urls can cause "open()" to fail in MSIE and Firefox. This would
      // normally have thrown an exception, but it has been caught and handled
      // to make this work uniform across browsers. Now that "send()" had been
      // called, fake a readystate change to 4 with status 0 (error):
      this._fake_readystatechange(4);
      return;
    }
    // Make a copy of the arguments for the users' convenience:
    this.body = body;
    try {
      if (typeof body == "undefined") {
          this._XMLHttpRequest.send(); // no body supplied
      } else {
        this._XMLHttpRequest.send(body);
      }
    } catch (e) {
      if (typeof(e.NETWORK_ERR) != "undefined" && e.NETWORK_ERR == 101) {
        // Safari throws an error if the network name cannot be resolved. The
        // readyState has already been changed to 1 and 4 and the status is set
        // to 0. There is no need for calling event handlers or anything else:
        return;
      } else if (typeof(e.message) != "undefined" && 
                 e.message.match("^Security violation")) {
        // Opera throws an error if the request is cross-origin. To make this
        // work uniform across all browsers the exception is handle and a
        // readystate change to 4 with a status of 0 is "faked" when the
        // readystate changes to 2:
        this._opera_send_security_violation = true;
        return;
      } else {
        // This is not an exception that should be handled, pass it along:
        dump_exception(e, this);
        throw e;
      }
    }
    // Update the attributes of the AsyncXMLHttpRequest object to those of the
    // XMLHttpRequest object:
    AsyncXMLHttpRequest._copy_attributes(this);
    // If the "timeout" attribute is set, record the start time and start a
    // timer to detect a timeout:
    if (this.timeout != 0) {
      // Record the start time of the request:
      this._start_time = new Date().valueOf();
      // Start a timer
      this._timer = setTimeout(function AsyncXMLHttpRequest_timer() {
        // Check all requests - it's unknown for which request the event fired:
        for (var i in AsyncXMLHttpRequest._instances) {
          var instance = AsyncXMLHttpRequest._instances[i];
          // Make sure this request even has a timer:
          if (typeof(instance._timer) == "undefined") continue;
          // Timers are not very precise and may even fire too soon. We'll clear
          // all timers and start them again. That way, if one fires too soon,
          // nothing will happen now, but a new timer will be created to fire
          // closer to the right time.
          clearTimeout(instance._timer);
          // Calculate how long ago/until the timeout:
          var time_until_timeout = instance._start_time + instance.timeout - 
              new Date().valueOf();
          if (time_until_timeout <= 0) {
            // This request has timed out: remove the timer and abort the
            // request:
            delete instance._timer;
            instance.timedout = true;
            instance.abort();
          } else {
            // Otherwise, recreate the timer we removed and make it fire at the
            // right time:
            setTimeout(arguments.callee, time_until_timeout);
          }
        }
      }, this.timeout);
    }
    
  }
  // Wrapper for abort()
  this.abort = function AsyncXMLHttpRequest_abort() {
    this._XMLHttpRequest.abort();
    // Update the attributes of the AsyncXMLHttpRequest object to those of the
    // XMLHttpRequest object:
    AsyncXMLHttpRequest._copy_attributes(this);
  }
  // Create an event handler for "readyState" changes of the XMLHttpRequest
  // object. The handler determines which object the event fired for by checking
  // all active objects to see if the "readyState" atttribute of the 
  // AsyncXMLHttpRequest does not match that of its XMLHttpRequest object.
  // It then calls the appropriate event handler(s) set by the user:
  this._XMLHttpRequest.onreadystatechange = function 
      AsyncXMLHttpRequest_onreadystatechange() {
    // Check all requests - it's unknown for which request the event fired:
    for (var i in AsyncXMLHttpRequest._instances) {
      var instance = AsyncXMLHttpRequest._instances[i];
      // Check if the readystate of the wrapped XHR object is now HIGHER than
      // the public value (the public value may be faked to be higher for
      // cross browser compatibility):
      if (instance.readyState < instance._XMLHttpRequest.readyState) {
        // Copy the new readyState, status and other properties:
        AsyncXMLHttpRequest._copy_attributes(instance);
        // MSIE has readyState 0, no other browser does. So readyState changes
        // to 0 are ignored to make this uniform across browsers:
        if (instance.readyState != 0) {
          // Call the onreadystatechange of the instance, if there is one:
          if (typeof(instance.onreadystatechange) != "undefined") {
            instance.onreadystatechange(instance);
          }
        }
        if (instance.readyState == 2 && 
            instance._opera_send_security_violation) {
            instance._fake_readystatechange(4);
        // Check if the request is done:
        } else if (instance.readyState == 4) {
          // When the request is done, it will no longer receive events. It can
          // be removed from the global list to prevent memory leaks:
          delete AsyncXMLHttpRequest._instances[i];
          // If there is a timeout, check it:
          if (instance.timeout != 0) {
            // If this request has timed out, call ontimeout, if it exists:
            if (instance.timedout) {
              if (typeof(instance.ontimeout) != "undefined") {
                instance.ontimeout(instance);
              }
              // Continue to prevent calling the "onerror" handler as well.
              continue;
            } else {
              // The request did not time out, but it has a timer: clear and
              // remove the timer:
              clearTimeout(instance._timer);
              delete instance._timer
            }
          }
          // Check if the request succeeded or not and call the right event
          // handler:
          if (instance.status >= 200 && instance.status <= 299) {
            // Call onload if the status is successful and it exists:
            if (typeof(instance.onload) != "undefined") {
              instance.onload(instance);
            }
          } else {
            // Call onerror if the status is unsuccessful and it exists:
            if (typeof(instance.onerror) != "undefined") {
              instance.onerror(instance);
            }
          }
        }
      }
    }
  }
}
